/*
 * Chastoslov
 * Copyright (C) 2009 Alexandr Stroghov
 * 
 * stroghov@gmail.com
 * 
 * This program is free software; you can redistribute it and/or modify it under the terms of the 
 * GNU General Public License, version 2, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program; if 
 * not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
 * MA 02111-1307 USA
*/
package world.ah.wf.sp;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.Types;

public class SP { 
  private static Connection conn;
  private static Connection getConn() throws SQLException {
    if(conn == null) conn = DriverManager.getConnection("jdbc:default:connection");
    return conn;
  }
  /**
   * Возвращает последний сгенерированный идентификатор 
   * @return идентификатор 
   * @throws java.sql.SQLException
   */
  private static int getLastId() throws SQLException { 
    Statement st = getConn().createStatement(ResultSet.TYPE_FORWARD_ONLY, 
      ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); 
    ResultSet rs = st.executeQuery("values identity_val_local()");
    rs.next();
    int i = rs.getInt(1);
    rs.close();
    return i;
  }
  /**
   * Записывает слово, если оно еще не существует.
   * @param word Слово
   * @return ID существующего или вновь записанного слова
   * @throws java.sql.SQLException
   */
  public static UpsertedObject upsertWord(String word) throws SQLException { 
    // Ищем слово 
    PreparedStatement prst = 
      getConn().prepareStatement("select ID from WORD where WORD = ?", ResultSet.TYPE_FORWARD_ONLY, 
        ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
    prst.setString(1, word);
    ResultSet rs = prst.executeQuery();
    // Добавляем слово, если его еще нет
    int id;
    boolean inserted;
    if(rs.next()) {
      id = rs.getInt(1);
      inserted = false;
    } else {
      prst = getConn().prepareStatement("insert into WORD (WORD) values (?)"); 
      prst.setString(1, word);
      prst.executeUpdate();
      id = getLastId(); 
      inserted = true;
    };
    rs.close();
    return new UpsertedObject(id, inserted);
  }
  /** Загружает слово с привязкой к тексту
   * @param textId Текст
   * @param word Слово
   */
  public static void uploadWord(int textId, String word) throws Exception { 
    // Обеспечиваем существование слова 
    UpsertedObject upsertedWord = upsertWord(word);
    // Обеспечили
    PreparedStatement st;
    ResultSet rs;
    // Решаем, добавлять или изменять появление
    boolean doInsert = true;
    if(!upsertedWord.getInserted()) {
      st = getConn().prepareStatement("select 1 from APPEARANCE where TEXT_ID = ? and WORD_ID = ?", 
        ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT);
      st.setInt(1, textId);
      st.setInt(2, upsertedWord.getId());
      rs = st.executeQuery();
      if(rs.next()) doInsert = false;
      rs.close();
    };
    // Решили 
    if(doInsert) st = getConn().prepareStatement("insert into APPEARANCE (TEXT_ID, WORD_ID, " + 
      "COUNT) values (?, ?, 1)"); 
    else st = getConn().prepareStatement("update APPEARANCE set COUNT = COUNT + 1 where " + 
      "TEXT_ID = ? and WORD_ID = ?"); 
    // 
    st.setInt(1, textId);
    st.setInt(2, upsertedWord.getId());
    st.executeUpdate(); 
    st.close();
  }
  /**
   * Помечает слово как скрытое или не скрытое 
   * @param wordId ID слова
   * @param done Пометить слово?
   * @throws java.lang.Exception
   */
  public static void setWordDone(int wordId, String done) throws Exception {
    // Проверка пераметров 
    if(!done.equals("Y") && !done.equals("N")) throw new Exception("Parameter 'DONE' must be Y/N");
    //
    //Connection con = DriverManager.getConnection("jdbc:default:connection");
    // Узнаем, скрыто ли слово
    PreparedStatement prst = getConn().prepareStatement("select 1 from DONE where WORD_ID = ? " + 
      "and READER_CODE = user", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, 
      ResultSet.CLOSE_CURSORS_AT_COMMIT); 
    prst.setInt(1, wordId);
    ResultSet rs = prst.executeQuery();
    boolean isDone = rs.next();
    rs.close();
    // Узнали
    if(isDone ^ done.equals("Y")) {
      if(done.equals("Y")) 
        prst = getConn().prepareStatement("insert into DONE (WORD_ID, READER_CODE) values (?, user)");
      else 
        prst = getConn().prepareStatement("delete from DONE where WORD_ID = ? and READER_CODE = user");
      prst.setInt(1, wordId);
      prst.executeUpdate(); 
    }
  }
  /**
   * Обеспечивает пометку слова как скрытого. Если слова не существует, то оно добавляется.
   * @param word слово
   * @throws java.lang.Exception
   */
  public static void setWordDone(String word) throws Exception {
    // Проверка пераметров 
    if(word.length() < 1) throw new Exception("Word must be defined");
    // Обеспечиваем существование слова
    UpsertedObject upsertedWord = upsertWord(word);
    // Обеспечили
    setWordDone(upsertedWord.getId(), "Y");
  }
  /**
   * Обертка над java.lang.String.replaceAll(String regex, String replacement).
   * @param word исходная строка
   * @param what что земенить
   * @param toWhat на что заменить
   * @return исходная строка с выполненными заменами
   */
  public static String replaceAll(String word, String what, String toWhat) {
    return word.replaceAll(what, toWhat);
  }
  /**
   * Добавляет или изменяет текст в зависимости от параметров.
   * @param whatToDo Действие, которое требуется выполнить (I/U - добавить/обновить).
   * @param MоdifiedInternally Было ли в процессе выполнения изменено значение хотя бы одного поля (Y/N).
   * @param id ID текста.
   * @param title Название текста.
   * @param note Примечание текста.
   */
  public static void upsertText(String whatToDo, String[] MоdifiedInternally, int[] id, 
    String[] title, String[] note) throws Exception
  {
    if(!whatToDo.equalsIgnoreCase("I") && !whatToDo.equalsIgnoreCase("U")) 
      throw new Exception("parameter 'whatToDo' must be I/U");
    //
    title[0] = title[0].trim();
    note[0] = note[0].trim();
    MоdifiedInternally[0] = "Y";
    //
    String sql;
    PreparedStatement st;
    if(whatToDo.equalsIgnoreCase("U")) {
      if(id[0] < 1) throw new Exception("id must be more then zero when updating");
      sql = "update TEXT set TITLE = ?, NOTE = ? where ID = ?";
      st = getConn().prepareStatement(sql);
      st.setString(1, title[0]); 
      st.setString(2, note[0]); 
      st.setInt(3,id[0]);
      int rowCount = st.executeUpdate();
      if(rowCount < 1) 
        throw new Exception("Заданный объект не найден. Ни одна запись не была изменена.");
    } 
    else {
      sql = "insert into TEXT (TITLE, NOTE) values (?, ?)";
      st = getConn().prepareStatement(sql);
      st.setString(1, title[0]); 
      st.setString(2, note[0]); 
      st.executeUpdate();
      id[0] = getLastId();
    }
  }
  /**
   * Добавляет пользователя и регистрирует его как читателя.
   * @param result результат: 3 - пользователь был добавлен, читатель - зарегистрирован;
   *                          2 - пользователь был добавлен, но читатель  не зарегистрирован, 
   *                              поскольку он уже существовал до начала выполнения операции;
   *                          1 - читатель зарегистрирован, но пользователь не добавлен, 
   *                              поскольку он уже существовал до начала выполнения операции;
   *                          0 - и пользователь и читатель уже существовали до начала выполнения 
       *                          операции, ничего изменено не было.
   * @param login учетное имя пользователя.
   * @param password пароль пользователя.
   * @param readerName имя читателя.
   * @throws java.lang.Exception 
   */
  public static void createNewUser(int[] result, String login, String password, 
    String readerName) throws Exception
  {
    if(login == null || login.length() < 1) throw new Exception("Empty login is not allowed");
    else if(password == null || password.length() < 1) 
      throw new Exception("Empty password is not allowed");
    //
    login = login.trim().toLowerCase();
    if(!login.matches("^[a-z]{1}[a-z0-9_]*")) 
      throw new Exception("Login contains illegal symbols (alowed 'a'-'z', '0'-'9' and '_' " + 
        "only, the first must be 'a'-'z').");
    result[0] = 0;
    //
    PreparedStatement prst;
    ResultSet rs;
    // Узнаем, есть ли уже такой пользователь
    CallableStatement clst;
    clst = getConn().prepareCall("{? = call syscs_util.syscs_get_database_property(?)}");
    clst.registerOutParameter(1, Types.VARCHAR);
    clst.setString(2, "derby.user." + login);
    clst.executeUpdate();
    String pwd = clst.getString(1);
    clst.close();
    // Если нет, то добавляем его
    if(pwd == null || pwd.equals("")) {
      clst = getConn().prepareCall("{call syscs_util.syscs_set_database_property(?, ?)}");
      clst.setString(1, "derby.user." + login);
      clst.setString(2, password);
      clst.executeUpdate();
      clst.close();
      result[0] |= 2;
    }
    // Узнаем, есть ли уже такой читатель 
    login = login.toUpperCase();
    prst = getConn().prepareStatement("select 1 from READER where CODE = ?", 
      ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT); 
    prst.setString(1, login);
    rs = prst.executeQuery();
    boolean readerExists = rs.next();
    rs.close();
    prst.close();
    // Если нет, то добавляем его 
    if(!readerExists) {
      prst = getConn().prepareStatement("insert into READER (CODE, NAME) values (?, ?)"); 
      prst.setString(1, login);
      prst.setString(2, readerName);
      prst.executeUpdate();
      result[0] |= 1;
    }
  }
  /**
   * Удаляет текущего пользователя программы. Пользователю запрещается вход в БД, а также 
   * удаляется его регистрация в программе, вместе со списком всех его скрытых слов.
   * @throws java.lang.Exception
   */
  public static void deleteCurrentUser() throws Exception {
    PreparedStatement prst;
    CallableStatement clst;
    // Удаляем регистрацию читателя (вместе со списков его скрытых слов)
    prst = getConn().prepareStatement("delete from READER where CODE = user"); 
    prst.executeUpdate();
    prst.close();
    // Удаляет пользователя из БД
    clst = getConn().prepareCall("{call syscs_util.syscs_set_database_property" +
      "('derby.user.' || lower(user), null)}");
    clst.executeUpdate();
    clst.close();
  }
  
}

